package com.zcy.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;

/**
 * 阿里云OSS文件上传工具类
 */
@Slf4j
public class AliOssUtil {
    
    /**
     * 使用默认配置上传文件到阿里云OSS（公网访问）
     *
     * @param objectName  文件名（包含路径）
     * @param inputStream 文件输入流
     * @return 文件访问URL，如果上传失败则返回null
     */
    public static String upload(String objectName, InputStream inputStream) {
        return upload(OssConfig.PUBLIC_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                OssConfig.BUCKET_NAME, objectName, inputStream);
    }
    
    /**
     * 使用默认配置上传文件到阿里云OSS（公网访问），带重复上传检查
     *
     * @param objectName  文件名（包含路径）
     * @param inputStream 文件输入流
     * @return 文件访问URL，如果上传失败则返回null
     */
    public static String uploadWithDeduplication(String objectName, InputStream inputStream) {
        try {
            // 计算文件的MD5值
            String fileMd5 = calculateMD5(inputStream);
            // 构造带MD5值的文件名
            String md5ObjectName = "md5/" + fileMd5;
            
            // 检查是否已经有相同MD5的文件
            if (doesObjectExist(md5ObjectName)) {
                // 如果存在，则获取已有的文件URL
                String existingUrl = OssConfig.PUBLIC_URL_PREFIX + "/" + objectName;
                log.info("文件已存在，返回已存在的文件URL: {}", existingUrl);
                return existingUrl;
            }
            
            // 重置输入流
            inputStream.reset();
            
            // 上传文件
            String url = upload(OssConfig.PUBLIC_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                    OssConfig.BUCKET_NAME, objectName, inputStream);
            
            // 如果上传成功，同时上传一个MD5标识文件
            if (url != null) {
                // 创建一个空的MD5标识文件
                OSS ossClient = new OSSClientBuilder().build(OssConfig.PUBLIC_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET);
                try {
                    PutObjectRequest putObjectRequest = new PutObjectRequest(OssConfig.BUCKET_NAME, md5ObjectName, new java.io.ByteArrayInputStream(new byte[0]));
                    ossClient.putObject(putObjectRequest);
                } finally {
                    ossClient.shutdown();
                }
            }
            
            return url;
        } catch (Exception e) {
            log.error("上传文件时发生异常", e);
            return null;
        }
    }
    
    /**
     * 使用默认配置通过内网上传文件到阿里云OSS
     *
     * @param objectName  文件名（包含路径）
     * @param inputStream 文件输入流
     * @return 文件访问URL，如果上传失败则返回null
     */
    public static String uploadInternal(String objectName, InputStream inputStream) {
        return upload(OssConfig.INTERNAL_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                OssConfig.BUCKET_NAME, objectName, inputStream);
    }
    
    /**
     * 使用默认配置通过内网上传文件到阿里云OSS，带重复上传检查
     *
     * @param objectName  文件名（包含路径）
     * @param inputStream 文件输入流
     * @return 文件访问URL，如果上传失败则返回null
     */
    public static String uploadInternalWithDeduplication(String objectName, InputStream inputStream) {
        try {
            // 计算文件的MD5值
            String fileMd5 = calculateMD5(inputStream);
            // 构造带MD5值的文件名
            String md5ObjectName = "md5/" + fileMd5;
            
            // 检查是否已经有相同MD5的文件
            if (doesObjectExistInternal(md5ObjectName)) {
                // 如果存在，则获取已有的文件URL
                String existingUrl = OssConfig.INTERNAL_URL_PREFIX + "/" + objectName;
                log.info("文件已存在，返回已存在的文件URL: {}", existingUrl);
                return existingUrl;
            }
            
            // 重置输入流
            inputStream.reset();
            
            // 上传文件
            String url = upload(OssConfig.INTERNAL_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                    OssConfig.BUCKET_NAME, objectName, inputStream);
            
            // 如果上传成功，同时上传一个MD5标识文件
            if (url != null) {
                // 创建一个空的MD5标识文件
                OSS ossClient = new OSSClientBuilder().build(OssConfig.INTERNAL_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET);
                try {
                    PutObjectRequest putObjectRequest = new PutObjectRequest(OssConfig.BUCKET_NAME, md5ObjectName, new java.io.ByteArrayInputStream(new byte[0]));
                    ossClient.putObject(putObjectRequest);
                } finally {
                    ossClient.shutdown();
                }
            }
            
            return url;
        } catch (Exception e) {
            log.error("上传文件时发生异常", e);
            return null;
        }
    }
    
    /**
     * 上传文件到阿里云OSS
     *
     * @param endpoint        OSS区域endpoint
     * @param accessKeyId     访问密钥ID
     * @param accessKeySecret 访问密钥密码
     * @param bucketName      存储空间名称
     * @param objectName      文件名（包含路径）
     * @param inputStream     文件输入流
     * @return 文件访问URL，如果上传失败则返回null
     */
    public static String upload(String endpoint, String accessKeyId, String accessKeySecret,
                                String bucketName, String objectName, InputStream inputStream) {
        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
        try {
            // 创建PutObjectRequest对象
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
            
            // 上传文件
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            
            // 拼接文件访问路径
            String url = generatePresignedUrl(endpoint, bucketName, objectName, accessKeyId, accessKeySecret);
            log.info("文件上传成功，文件路径: {}", url);
            return url;
        } catch (OSSException oe) {
            log.error("OSS异常，错误码: {}, 错误信息: {}", oe.getErrorCode(), oe.getMessage());
        } catch (ClientException ce) {
            log.error("客户端异常，错误信息: {}", ce.getMessage());
        } catch (Exception e) {
            log.error("文件上传异常", e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        
        return null;
    }
    
    /**
     * 使用默认配置生成预签名URL（公网访问，私有bucket适用）
     *
     * @param objectName 文件名
     * @return 预签名URL
     */
    public static String generatePresignedUrl(String objectName) {
        return generatePresignedUrl(OssConfig.PUBLIC_ENDPOINT, OssConfig.BUCKET_NAME, objectName,
                OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET);
    }
    
    /**
     * 使用默认配置生成预签名URL（内网访问，私有bucket适用）
     *
     * @param objectName 文件名
     * @return 预签名URL
     */
    public static String generatePresignedUrlInternal(String objectName) {
        return generatePresignedUrl(OssConfig.INTERNAL_ENDPOINT, OssConfig.BUCKET_NAME, objectName,
                OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET);
    }
    
    /**
     * 生成预签名URL（私有bucket适用）
     *
     * @param endpoint        OSS区域endpoint
     * @param bucketName      存储空间名称
     * @param objectName      文件名
     * @param accessKeyId     访问密钥ID
     * @param accessKeySecret 访问密钥密码
     * @return 预签名URL
     */
    public static String generatePresignedUrl(String endpoint, String bucketName, String objectName,
                                              String accessKeyId, String accessKeySecret) {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        try {
            // 设置签名URL过期时间（1小时）
            Date expiration = new Date(new Date().getTime() + 3600 * 1000);
            URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
            return url.toString();
        } finally {
            ossClient.shutdown();
        }
    }
    
    /**
     * 使用默认配置删除OSS上的文件（公网访问）
     *
     * @param objectName 文件名
     * @return 删除结果，true表示删除成功
     */
    public static boolean delete(String objectName) {
        return delete(OssConfig.PUBLIC_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                OssConfig.BUCKET_NAME, objectName);
    }
    
    /**
     * 使用默认配置通过内网删除OSS上的文件
     *
     * @param objectName 文件名
     * @return 删除结果，true表示删除成功
     */
    public static boolean deleteInternal(String objectName) {
        return delete(OssConfig.INTERNAL_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                OssConfig.BUCKET_NAME, objectName);
    }
    
    /**
     * 删除OSS上的文件
     *
     * @param endpoint        OSS区域endpoint
     * @param accessKeyId     访问密钥ID
     * @param accessKeySecret 访问密钥密码
     * @param bucketName      存储空间名称
     * @param objectName      文件名
     * @return 删除结果，true表示删除成功
     */
    public static boolean delete(String endpoint, String accessKeyId, String accessKeySecret,
                                 String bucketName, String objectName) {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
        try {
            // 检查文件是否存在
            boolean exists = ossClient.doesObjectExist(bucketName, objectName);
            if (!exists) {
                log.warn("文件不存在: {}", objectName);
                return false;
            }
            
            // 删除文件
            ossClient.deleteObject(bucketName, objectName);
            log.info("文件删除成功: {}", objectName);
            return true;
        } catch (OSSException oe) {
            log.error("删除文件时发生OSS异常，错误码: {}, 错误信息: {}", oe.getErrorCode(), oe.getMessage());
        } catch (ClientException ce) {
            log.error("删除文件时发生客户端异常，错误信息: {}", ce.getMessage());
        } catch (Exception e) {
            log.error("删除文件时发生未知异常", e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        
        return false;
    }
    
    /**
     * 使用默认配置检查文件是否存在（公网访问）
     *
     * @param objectName 文件名
     * @return true表示存在，false表示不存在
     */
    public static boolean doesObjectExist(String objectName) {
        return doesObjectExist(OssConfig.PUBLIC_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                OssConfig.BUCKET_NAME, objectName);
    }
    
    /**
     * 使用默认配置通过内网检查文件是否存在
     *
     * @param objectName 文件名
     * @return true表示存在，false表示不存在
     */
    public static boolean doesObjectExistInternal(String objectName) {
        return doesObjectExist(OssConfig.INTERNAL_ENDPOINT, OssConfig.ACCESS_KEY_ID, OssConfig.ACCESS_KEY_SECRET,
                OssConfig.BUCKET_NAME, objectName);
    }
    
    /**
     * 检查文件是否存在
     *
     * @param endpoint        OSS区域endpoint
     * @param accessKeyId     访问密钥ID
     * @param accessKeySecret 访问密钥密码
     * @param bucketName      存储空间名称
     * @param objectName      文件名
     * @return true表示存在，false表示不存在
     */
    public static boolean doesObjectExist(String endpoint, String accessKeyId, String accessKeySecret,
                                          String bucketName, String objectName) {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
        try {
            return ossClient.doesObjectExist(bucketName, objectName);
        } catch (Exception e) {
            log.error("检查文件是否存在时发生异常", e);
            return false;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
    
    /**
     * 计算输入流的MD5值
     *
     * @param inputStream 输入流
     * @return MD5值
     * @throws Exception 计算过程中可能抛出的异常
     */
    private static String calculateMD5(InputStream inputStream) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            md.update(buffer, 0, bytesRead);
        }
        byte[] digest = md.digest();
        
        // 转换为十六进制字符串
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}